diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-30 18:17:45 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-30 18:18:15 +0200 |
| commit | ed02ba872c02eafed45f406c68d389d0a1112a4e (patch) | |
| tree | 40530d77cfe306621915f067c66531cb9e783a4d /apps/web/src/app/editor/[project_id] | |
| parent | e837e7504379e100ff3b56282577ec67bb88bec9 (diff) | |
| download | opencut-ed02ba872c02eafed45f406c68d389d0a1112a4e.tar.gz | |
feat: Electron desktop app, native ffmpeg export, fully static web
- apps/desktop: Electron shell serving the static web build over a
privileged opencut:// protocol; exports stream frames over IPC to a
bundled ffmpeg (libx264/libvpx-vp9, BT.709 tagged) with backpressure.
electron-builder packaging: AppImage, deb, rpm.
- apps/web: dead backend deleted (better-auth, drizzle/pg, upstash,
botid, freesound proxy, blog, rss, env validation); next build emits
a zero-env static export; /editor/[project_id] -> /editor?project=.
- Native exporter mirrors SceneExporter; activates when the preload
bridge is present; web keeps the mediabunny path.
- Docker self-hosting now builds and serves the static export via nginx.
- Release workflow: v* tags publish static web tarball + Linux packages.
- Changelog 0.4.0; README/AGENTS/CONTRIBUTING/roadmap updated.
- GPUI/Rust rewrite parked on branch rust-rewrite.
Diffstat (limited to 'apps/web/src/app/editor/[project_id]')
| -rw-r--r-- | apps/web/src/app/editor/[project_id]/page.tsx | 209 |
1 files changed, 0 insertions, 209 deletions
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx deleted file mode 100644 index b759b9c2..00000000 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ /dev/null @@ -1,209 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, -} from "@/components/ui/resizable"; -import { AssetsPanel } from "@/components/editor/panels/assets"; -import { PropertiesPanel } from "@/components/editor/panels/properties"; -import { Timeline } from "@/timeline/components"; -import { PreviewPanel } from "@/preview/components"; -import { EditorHeader } from "@/components/editor/editor-header"; -import { EditorProvider } from "@/components/providers/editor-provider"; -import { Onboarding } from "@/components/editor/onboarding"; -import { MigrationDialog } from "@/project/components/migration-dialog"; -import { usePanelStore } from "@/editor/panel-store"; -import { usePasteMedia } from "@/media/use-paste-media"; -import { MobileGate } from "@/components/editor/mobile-gate"; -import { useMemo, useState } from "react"; -import { useEditor } from "@/editor/use-editor"; -import { Cancel01Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { Button } from "@/components/ui/button"; -import { ChangelogNotification } from "@/changelog/components/changelog-notification"; -import { - createPreviewOverlayControl, - isPreviewOverlayVisible, - mergePreviewOverlaySources, -} from "@/preview/overlays"; -import { usePreviewStore } from "@/preview/preview-store"; -import { getGuidePreviewOverlaySource } from "@/guides"; -import { - bookmarkNotesPreviewOverlay, - getBookmarkPreviewOverlaySource, -} from "@/timeline/bookmarks/index"; - -export default function Editor() { - const params = useParams<{ project_id: string }>(); - const projectId = params.project_id; - - return ( - <MobileGate> - <EditorProvider projectId={projectId}> - <div className="bg-background flex h-screen w-screen flex-col overflow-hidden"> - <DegradedRendererBanner /> - <EditorHeader /> - <div className="min-h-0 min-w-0 flex-1"> - <EditorLayout /> - </div> - <Onboarding /> - <MigrationDialog /> - <ChangelogNotification /> - </div> - </EditorProvider> - </MobileGate> - ); -} - -function DegradedRendererBanner() { - const isDegraded = useEditor((e) => e.renderer.isDegraded); - const [dismissed, setDismissed] = useState(false); - if (!isDegraded || dismissed) return null; - - return ( - <div className="bg-accent border-b h-9 flex items-center justify-center gap-2 text-xs text-muted-foreground"> - <span>For the best experience, open OpenCut in Chrome.</span> - <Button - variant="text" - size="icon" - className="p-0 w-auto [&_svg]:size-3.5" - onClick={() => setDismissed(true)} - aria-label="Dismiss" - > - <HugeiconsIcon icon={Cancel01Icon} /> - </Button> - </div> - ); -} - -function EditorLayout() { - usePasteMedia(); - const { panels, setPanel } = usePanelStore(); - const activeScene = useEditor((editor) => - editor.scenes.getActiveSceneOrNull(), - ); - const currentTime = useEditor((editor) => editor.playback.getCurrentTime()); - const activeGuide = usePreviewStore((state) => state.activeGuide); - const overlays = usePreviewStore((state) => state.overlays); - const setOverlayVisibility = usePreviewStore( - (state) => state.setOverlayVisibility, - ); - const showBookmarkNotes = isPreviewOverlayVisible({ - overlay: bookmarkNotesPreviewOverlay, - overlays, - }); - - const overlaySource = useMemo( - () => - mergePreviewOverlaySources({ - sources: [ - getGuidePreviewOverlaySource({ - guideId: activeGuide, - }), - activeScene - ? getBookmarkPreviewOverlaySource({ - bookmarks: activeScene.bookmarks, - time: currentTime, - isVisible: showBookmarkNotes, - }) - : { - definitions: [bookmarkNotesPreviewOverlay], - instances: [], - }, - ], - }), - [activeGuide, activeScene, currentTime, showBookmarkNotes], - ); - - const overlayControls = useMemo( - () => - overlaySource.definitions.map((overlay) => - createPreviewOverlayControl({ overlay, overlays }), - ), - [overlaySource.definitions, overlays], - ); - - return ( - <ResizablePanelGroup - direction="vertical" - className="size-full gap-[0.18rem]" - onLayout={(sizes) => { - setPanel({ - panel: "mainContent", - size: sizes[0] ?? panels.mainContent, - }); - setPanel({ - panel: "timeline", - size: sizes[1] ?? panels.timeline, - }); - }} - > - <ResizablePanel - defaultSize={panels.mainContent} - minSize={30} - maxSize={85} - className="min-h-0" - > - <ResizablePanelGroup - direction="horizontal" - className="size-full gap-[0.19rem] px-3" - onLayout={(sizes) => { - setPanel({ panel: "tools", size: sizes[0] ?? panels.tools }); - setPanel({ panel: "preview", size: sizes[1] ?? panels.preview }); - setPanel({ - panel: "properties", - size: sizes[2] ?? panels.properties, - }); - }} - > - <ResizablePanel - defaultSize={panels.tools} - minSize={15} - maxSize={40} - className="min-w-0" - > - <AssetsPanel /> - </ResizablePanel> - - <ResizableHandle withHandle /> - - <ResizablePanel - defaultSize={panels.preview} - minSize={30} - className="min-h-0 min-w-0 flex-1" - > - <PreviewPanel - overlayControls={overlayControls} - overlayInstances={overlaySource.instances} - onOverlayVisibilityChange={setOverlayVisibility} - /> - </ResizablePanel> - - <ResizableHandle withHandle /> - - <ResizablePanel - defaultSize={panels.properties} - minSize={15} - maxSize={40} - className="min-w-0" - > - <PropertiesPanel /> - </ResizablePanel> - </ResizablePanelGroup> - </ResizablePanel> - - <ResizableHandle withHandle /> - - <ResizablePanel - defaultSize={panels.timeline} - minSize={15} - maxSize={70} - className="min-h-0 px-3 pb-3" - > - <Timeline /> - </ResizablePanel> - </ResizablePanelGroup> - ); -} |
